﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swcommands;
using SolidWorks.Interop.swconst;
using SolidWorks.Interop.swpublished;

using SwAddinHelpers.Contracts;
using SwAddinHelpers.TaskPane;
using SwAddinHelpers.Forms;
using SwAddinHelpers.PropertyManager;
using SwAddinHelpers.Sinks;
using SwAddinHelpers.CommandManager;
using SwAddinHelpers.MenuManager;
using SwAddinHelpers.Base;
using SwAddinHelpers.Attributes;
using SwAddinHelpers.FeatureView;
using SwAddinHelpers.ModelView;
using SwAddinHelpers.ComHelpers;

using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Design;
using System.ComponentModel.Composition.Hosting;

using System.Reflection;
using System.Reflection.Emit;

namespace SwAddinBase
{
    // 64bit considerations: https://forum.solidworks.com/message/145878#145878
    // COM considerations: http://www.informit.com/articles/article.aspx?p=27219&seqNum=8
    //                      http://spec.winprog.org/typeinf2/
    //                      http://blogs.msdn.com/b/lucian/archive/2008/10/06/reflection-on-com-objects.aspx
    //                      http://www.dotnetmonster.com/Uwe/Forum.aspx/dotnet-interop/8249/Introspecting-COM-Object-in-C
    //                      http://www.jasonnewell.net/
    //                      http://msdn.microsoft.com/en-us/magazine/cc164085.aspx
    //                      http://msdn.microsoft.com/en-us/library/aa719516%28v=VS.71%29.aspx
    //                      http://msdn.microsoft.com/en-us/library/aa720205%28v=VS.71%29.aspx

    [Guid("78cf869a-9eb0-31b4-94f8-9fae533856f0")]
    [ComVisible(true)]
    [SwAddin(Description = "Generic Addin Framework R2", Title = "GAF-R2", LoadAtStartup = true)]
    [Export(typeof(ISwAddinBase))]
    public sealed class SwAddinBase : ComRegistrableAddinImpl, ISwAddin, ISwAddinBase, ISwAddinBaseDebug
    {
        #region MEF
        public CompositionContainer compositionContainer { get; set; }
        #endregion

        #region Application Elements
        private SldWorks _swApplication;
        private int _swCookie;

        public SldWorks SolidWorksApplication
        {
            get { return _swApplication; }
        }

        public int Cookie { get { return _swCookie; } }

        private string GetAssemblyLocation()
        {
            return System.Reflection.Assembly.GetExecutingAssembly().Location.Remove(System.Reflection.Assembly.GetExecutingAssembly().Location.LastIndexOf(@"\"));
        }
        
        public ISwAddinBaseDebug Debug
        {
            get { return this; }
        }

        public bool IsWindowsXP
        {
            get
            {
                if (System.Environment.OSVersion.Version.Major == 5)
                    return true;

                return false;
            }
        }

        #endregion

        #region UI Elements

        private CallbackPool _callbackPool;

        public CallbackPool CallbackPool
        {
            get { return _callbackPool; }
        }

        public List<UserControlHostBase> _HostCollection;

        public List<UserControlHostBase> HostCollection
        {
            get { return _HostCollection; }
        }

        public UserControlHostBase RetrieveUIHostByProgId(string ProgId)
        {
            UserControlHostBase ret = _HostCollection.Find(
                delegate(UserControlHostBase e)
                {
                    return e.HostedControlTypeProgId == ProgId;
                });

            return ret;
        }

        private string _PropertyPageHostProgId = null;

        public void ActivatePropertyPageHost()
        {
            if (_PropertyPageHostProgId != null)
            {
                ((FeatureViewHost)RetrieveUIHostByProgId(_PropertyPageHostProgId)).ActivatePmpHolder();
            }
        }

        #endregion

        #region Misc Elements

        private string _DUMMYFILE;
        public string DUMMYFILE
        {
            get { return _DUMMYFILE; }
        }

        private string _DUMMYBMP;
        public string DUMMYBMP
        {
            get { return _DUMMYBMP; }
        }

        private swAppEventSink _MainAppEventSink;

        public swAppEventSink MainAppEventSink { get { return _MainAppEventSink; } }

        private CommandManagerHost _CommandManagerHost;

        public CommandManagerHost CommandManagerHost { get { return _CommandManagerHost; } }

        private MenuManagerHost _MenuManagerHost;

        public MenuManagerHost MenuManagerHost { get { return _MenuManagerHost; } }
        #endregion

        #region Debug Elements
        public bool IsDebuggerAttached { get; set; }
        public bool loadForms { get; set; }
        public bool loadTP { get; set; }
        public bool loadFV { get; set; }
        public bool loadPMP { get; set; }
        public bool loadMV { get; set; }
        public bool loadMenu { get; set; }
        public bool loadCommand { get; set; }
        public bool buildProxy1 { get; set; }
        public bool Is0wned { get; set; }
        private SwAddinHelpers.AddinManager.DebugManager debugManager = null;
        public SwAddinHelpers.AddinManager.DebugManager Manager
        { 
            get 
            {
                if (debugManager == null)
                    debugManager = new SwAddinHelpers.AddinManager.DebugManager(this);

                return debugManager; 
            } 
        }
        private System.Threading.ManualResetEvent _mre1 = null;
        
        public void ContinueLoadingAddin()
        {
            try 
            { 
                _mre1.Set();
                if (_LoadStopWatch != null && ! _LoadStopWatch.IsRunning) 
                    _LoadStopWatch.Start();
            }
            catch { }
        }
         
        public void dmThread()
        {
            debugManager = new SwAddinHelpers.AddinManager.DebugManager(this);
            Application.Run(debugManager);
        }
        #endregion

        #region Constructor
        public SwAddinBase()
        {
            _DUMMYFILE = GetAssemblyLocation() + @"\dummy.sldprt";
            _DUMMYBMP = GetAssemblyLocation() + @"\dummy.bmp";

            _MainAppEventSink = new swAppEventSink(this);
        }
        #endregion

        #region ISwAddin Implementation
        
        private System.Diagnostics.Stopwatch _LoadStopWatch = null;

        public bool ConnectToSW(object ThisSW, int Cookie)
        {
            /*
            //byte[] b = new byte[] { 50, 0x3d, 0xa3, 0x83, 0xc5, 0x37, 0xce, 0x11, 0xbf, 0xd4, 0, 0x40, 5, 0x13, 0xbb, 0x57 };
            //Guid riid = new Guid(b);
            Type t  = Type.GetTypeFromCLSID(new Guid("83A33D32-37C5-11CE-BFD4-00400513BB57"), true);
            //Type t = Type.GetTypeFromCLSID(riid , true);
            //object o = Activator.CreateInstance(t);
            Guid _guid = Guid.NewGuid();
            AssemblyName asmname = new AssemblyName();
            asmname.Name = "sink1Asm" + _guid;
            AssemblyBuilder _asmBuilder = System.Threading.Thread.GetDomain().DefineDynamicAssembly(asmname, AssemblyBuilderAccess.Run);
            ModuleBuilder _modBuilder = _asmBuilder.DefineDynamicModule("sink1Module");
            TypeBuilder _typeBuilder = _modBuilder.DefineType("sink1Type", TypeAttributes.Public, null, new Type[] { t });
            Type _bakedType = _typeBuilder.CreateType();
            object _callbackTarget = Activator.CreateInstance(_bakedType);
            */

            _LoadStopWatch = new System.Diagnostics.Stopwatch();
            _LoadStopWatch.Start();
            
            Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

            // Allow cross-thread control access
            Control.CheckForIllegalCrossThreadCalls = false;

            _swApplication = (SldWorks)ThisSW;
            _swCookie = Cookie;

            UpdateComponents();

            if (_swApplication.GetDocumentCount() > 0)
            {
                MessageBox.Show("There are active documents open.  Save your documents and click on the 'GAF-R2' > 'Continue Loading...' menu to finish loading the addin.",  "Addin loading will be delayed.");
                DelayLoad();
                return true;
            }

            LoadComponents();

            _LoadStopWatch.Stop();
            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Addin loaded in: " + _LoadStopWatch.ElapsedMilliseconds + "ms");
            _LoadStopWatch = null;

            return true;
        }

        void OwnSolidWorks()
        {
            TaskpaneView tpv = _swApplication.CreateTaskpaneView2(null, null);
            Control c = (Control)tpv.AddControl("SwAddinBase.OwningSolidworks", "");
            ManagedWinapi.Windows.SystemWindow sw = new ManagedWinapi.Windows.SystemWindow(c);
            ManagedWinapi.Windows.SystemWindow swStatus = sw.Parent.Parent.Parent.Parent.Parent.Parent.Parent.AllChildWindows[3];
            IntPtr text0 = Marshal.StringToHGlobalAuto("507idW0rk5 owned by Generic Addin Framework");
            SwAddinHelpers.ComHelpers.pinvoke.SendMessage(swStatus.HWnd, SwAddinHelpers.ComHelpers.pinvoke.SB_SETTEXT, 0, text0);
            Marshal.FreeHGlobal(text0);
            tpv.HideView();
            tpv.DeleteView();
            Marshal.ReleaseComObject(tpv);
            tpv = null;
        }

        public void ThrowUnhandledException(Exception e)
        {
            MessageBox.Show(e.Message + "\r\n\r\nStack: " + e.StackTrace, "UNHANDLED EXCEPTION WAS CAUGHT!");
        }

        void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            ThrowUnhandledException(e.Exception);
        }

        private CommandManager _DelayedCommandManager;
        private CommandGroup _DelayedCommandGroup;

        private void DelayLoad()
        {
            _DelayedCommandManager = _swApplication.GetCommandManager(_swCookie);
            _DelayedCommandGroup = _DelayedCommandManager.CreateCommandGroup(666, "GAF-R2", "GAF-R2", "GAF-R2", -1);
            _DelayedCommandGroup.AddCommandItem2("Continue Loading...", 0, "Continue Loading...", "Continue Loading...", 0, "DelayedLoad", null, 0, (int)swCommandItemType_e.swMenuItem);
            _swApplication.SetAddinCallbackInfo(0, this, _swCookie);
            _DelayedCommandGroup.Activate();
        }

        private void DelayedLoad()
        {
            _DelayedCommandManager.RemoveCommandGroup(666);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(_DelayedCommandGroup);
            _DelayedCommandGroup = null;
            System.Runtime.InteropServices.Marshal.ReleaseComObject(_DelayedCommandManager);
            _DelayedCommandManager = null;
            GC.Collect();
            LoadComponents();
        }

        private void UpdateComponents()
        {
            string updateSource = GetAssemblyLocation() + @"\update\";
            string updateDestination = GetAssemblyLocation() + @"\";
            
            //todo
            // execute regasm /u
            // copy all files from updatesource to updatedestination
            // execute regasm
        }

        private void LoadDebugger()
        {
            IsDebuggerAttached = false;
            Is0wned = false;
            loadTP = true;
            loadForms = true;
            loadPMP = true;
            loadFV = true;
            loadMV = true;
            loadMenu = true;
            loadCommand = true;
            buildProxy1 = true;

            if (System.IO.File.Exists(GetAssemblyLocation() + @"\manager.debug"))
            {
                _mre1 = new System.Threading.ManualResetEvent(false);

                System.Threading.Thread dmT = new System.Threading.Thread(dmThread);
                dmT.Start();
                _LoadStopWatch.Stop();
                _mre1.WaitOne();
                IsDebuggerAttached = true;
            }
        }

        public void LoadComponents()
        {
            LoadDebugger();

            _MainAppEventSink.Tie();

            this.UISetup();

            if (Is0wned) OwnSolidWorks();
        }

        public bool DisconnectFromSW()
        {
            this.UITeardown();

            _MainAppEventSink.Untie();

            if (debugManager != null)
            {
                if (IsDebuggerAttached) MessageBox.Show("Addin is disconnecting.", "Generic Addin Framework");
                debugManager.Close();
                debugManager = null;
            }

            Application.ThreadException -= new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

            Marshal.ReleaseComObject(_swApplication);
            //GC.WaitForPendingFinalizers();
            GC.Collect();

            return true;
        }
        #endregion

        #region UI Setup and Teardown

        private void Compose()
        {
            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Composing External Components");
            DirectoryCatalog directoryCatalog = new DirectoryCatalog(GetAssemblyLocation());
            AssemblyCatalog assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            AggregateCatalog aggregateCatalog = new AggregateCatalog();
            aggregateCatalog.Catalogs.Add(directoryCatalog);
            aggregateCatalog.Catalogs.Add(assemblyCatalog);

            compositionContainer = new CompositionContainer(aggregateCatalog);
            compositionContainer.ComposeExportedValue((ISwAddinBase)this);            
            compositionContainer.ComposeParts();  
        }

        private void Decompose()
        {
            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Decomposing External Components");
            compositionContainer.Dispose();
            compositionContainer = null;
        }

        private void BuildExternalUserControls()
        {
            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Building External Controls");
            _HostCollection = new List<UserControlHostBase>();

            IEnumerable<IExternalUserControl> externalControls = compositionContainer.GetExportedValues<IExternalUserControl>().OrderByPropertyPages();

            foreach (IExternalUserControl ctl in externalControls)
            {
                HostedAsAttribute[] attribs = null;

                try
                {
                    attribs = ctl.ExternalUserControl.GetType().GetCustomAttributes(typeof(HostedAsAttribute), false);

                    foreach (HostedAsAttribute attrib in attribs)
                    {
                        Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Building Control", new string[]{ ctl.ExternalUserControl.GetType().FullName});

                        UserControlHostBase hostBase = null;

                        if (attrib.GetType() == typeof(HostedAsPropertyPage) &&
                            ctl.ExternalUserControl.GetType().BaseType == typeof(ActiveXUserControlBase) &&
                            loadPMP)
                        {
                            hostBase = new PropertyManagerHost(
                        		this,
                        		ctl,
                        		((HostedAsPropertyPage)attrib).Title,
                        		((HostedAsPropertyPage)attrib).Caption,
                        		((HostedAsPropertyPage)attrib).ToolTip,
                        		((HostedAsPropertyPage)attrib).Options,
                        		((HostedAsPropertyPage)attrib).IsHosted);
                        }

                        if (attrib.GetType() == typeof(HostedAsForm) &&
                            ctl.ExternalUserControl.GetType().BaseType == typeof(UserControlBase) &&
                            loadForms)
                        {
                            hostBase = new FormsHost(this, ctl, ((HostedAsForm)attrib).WindowCaption);
                        }

                        if (attrib.GetType() == typeof(HostedAsTaskpane) &&
                            ctl.ExternalUserControl.GetType().BaseType == typeof(TaskPaneControlBase) &&
                            loadTP)
                        {
                            hostBase = new TaskPaneHost(
                        		this,
                        		ctl,
                        		((HostedAsTaskpane)attrib).Bitmap,
                        		((HostedAsTaskpane)attrib).ToolTip);
                        }

                        if (attrib.GetType() == typeof(HostedAsFeatureView) &&
                            ctl.ExternalUserControl.GetType().BaseType == typeof(UserControlBase) &&
                            loadFV)
                        {
                            bool shouldHost = ((HostedAsFeatureView)attrib).IsHost;

                            if (shouldHost &&
                                _PropertyPageHostProgId != null)
                                shouldHost = false;

                            hostBase = new FeatureViewHost(
                                this,
                                ctl,
                                ((HostedAsFeatureView)attrib).BitMapFile,
                                ((HostedAsFeatureView)attrib).ToolTip,
                                ((HostedAsFeatureView)attrib).WhichPane,
                                ((HostedAsFeatureView)attrib).AutoGenerateView,
                                ((HostedAsFeatureView)attrib).SplitterPosition,
                                ((HostedAsFeatureView)attrib).NotifyUserOfCreationFailure,
                                shouldHost);

                            // set host only once!
                            if (((HostedAsFeatureView)attrib).IsHost && _PropertyPageHostProgId == null)
                                _PropertyPageHostProgId = hostBase.HostedControlTypeProgId;
                            
                        }

                        if (attrib.GetType() == typeof(HostedAsModelView) && 
                            ctl.ExternalUserControl.GetType().BaseType == typeof(UserControlBase) &&
                            loadMV)
                        {
                            hostBase = new ModelViewTabHost(
                                this,
                                ctl,
                                ((HostedAsModelView)attrib).UniqueName,
                                true,
                                true,
                                true);
                        }

                        if (hostBase != null)
                        {
                            hostBase.SubscribeGuiControlEvents();
                            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Built " + hostBase.HostedControlTypeProgId );
                            _HostCollection.Add(hostBase);
                        }
                        else
                        {
                            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Control NOT built.", new string [] {ctl.ExternalUserControl.GetType().FullName});
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Exception, "Import is not attributed, unable to assign it to host.",new string [] { ctl.ExternalUserControl.GetType().FullName});
                }
            }

        }

        private void DestroyExternalUserControls()
        {
            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Destroying External Controls");
            foreach (UserControlHostBase h in _HostCollection)
            {
                h.Teardown();
            }
            _HostCollection.Clear();
            _HostCollection = null;
        }

        private void BuildMenus()
        {
            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Building Menus");
            if (loadCommand) _CommandManagerHost = new CommandManagerHost(this);
            if (loadMenu) _MenuManagerHost = new MenuManagerHost(this);

            if (buildProxy1) _callbackPool = new CallbackPool(this);

            if(loadCommand) _CommandManagerHost.Build();
            if(loadMenu) _MenuManagerHost.Build();

            if (buildProxy1)
            {
                if (!_swApplication.SetAddinCallbackInfo(0, _callbackPool.CallbackTarget, Cookie))
                    MessageBox.Show("Your addin will not receive callbacks from SolidWorks menus or command bars!", "SetAddinCallbackInfo Failed");
                else
                    _swApplication.AddCallback(Cookie, "GeneralPurposeCallback");
            }

            if (loadCommand)  _CommandManagerHost.PostBuild();
            if (loadMenu) _MenuManagerHost.PostBuild();
            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Built Menus");
        }

        private void DestroyMenus()
        {
            Debug.Manager.PostEvent(SwAddinHelpers.AddinManager.enumDebugMsgType.Addin, "Destroying Menus");
            if (loadCommand) _CommandManagerHost.Destroy();
            if (loadMenu) _MenuManagerHost.Destroy();
            if (buildProxy1) _callbackPool.Destroy();
        }

        private void UISetup()
        {
            Compose();
            BuildExternalUserControls();
            BuildMenus();
        }
        
        private void UITeardown()
        {
            // it is not necessary to ubsubscribe events in user controls.  they are untied during teardown.
            DestroyExternalUserControls();
            DestroyMenus();
            Decompose();

            
        }
        #endregion  
    }

    #region SW Registration
    /// <summary>
    /// Register Addin with SolidWorks
    /// </summary>
    public abstract class ComRegistrableAddinImpl
    {
        #region Register with COM
        [ComRegisterFunctionAttribute]
        public  static void RegisterClass(Type t)
        {
            #region Get Custom Attribute: SwAddinAttribute
            SwAddinAttribute SWattr = null;
            Type type = typeof(SwAddinBase);
            foreach (System.Attribute attr in type.GetCustomAttributes(false))
            {
                if (attr is SwAddinAttribute)
                {
                    SWattr = attr as SwAddinAttribute;
                    break;
                }
            }
            #endregion

            Microsoft.Win32.RegistryKey hklm = Microsoft.Win32.Registry.LocalMachine;
            Microsoft.Win32.RegistryKey hkcu = Microsoft.Win32.Registry.CurrentUser;

            string keyname = "SOFTWARE\\SolidWorks\\Addins\\{" + t.GUID.ToString() + "}";
            Microsoft.Win32.RegistryKey addinkey = hklm.CreateSubKey(keyname);
            addinkey.SetValue(null, 0);

            addinkey.SetValue("Description", SWattr.Description);
            addinkey.SetValue("Title", SWattr.Title);

            keyname = "Software\\SolidWorks\\AddInsStartup\\{" + t.GUID.ToString() + "}";
            addinkey = hkcu.CreateSubKey(keyname);
            addinkey.SetValue(null, Convert.ToInt32(SWattr.LoadAtStartup), Microsoft.Win32.RegistryValueKind.DWord);
        }

        [ComUnregisterFunctionAttribute]
        public  static void UnregisterClass(Type t)
        {
            Microsoft.Win32.RegistryKey hklm = Microsoft.Win32.Registry.LocalMachine;
            Microsoft.Win32.RegistryKey hkcu = Microsoft.Win32.Registry.CurrentUser;

            string keyname = "SOFTWARE\\SolidWorks\\Addins\\{" + t.GUID.ToString() + "}";
            try { hklm.DeleteSubKey(keyname); }
            catch { }

            keyname = "Software\\SolidWorks\\AddInsStartup\\{" + t.GUID.ToString() + "}";
            try { hkcu.DeleteSubKey(keyname); }
            catch { }
        }
        #endregion
    }
    #endregion

    #region OwningSw
    [ComVisible(true)]
    [ProgId("SwAddinBase.OwningSolidworks")]
    public sealed class OwningSolidworks : UserControl
    {

    }
    #endregion
}
